﻿using System;
using System.Collections.Generic;
using System.Text;

using Tao.OpenGl;

namespace RSEngine.Helper.OGL
{

    #region enum : PixelInternalFormat

    public enum PixelInternalFormat
    {
        One = 1,
        Two = 2,
        Three = 3,
        Four = 4,
        DepthComponent = 6402,
        Alpha = 6406,
        Rgb = 6407,
        Rgba = 6408,
        Luminance = 6409,
        LuminanceAlpha = 6410,
        R3G3B2 = 10768,
        Alpha4 = 32827,
        Alpha8 = 32828,
        Alpha12 = 32829,
        Alpha16 = 32830,
        Luminance4 = 32831,
        Luminance8 = 32832,
        Luminance12 = 32833,
        Luminance16 = 32834,
        Luminance4Alpha4 = 32835,
        Luminance6Alpha2 = 32836,
        Luminance8Alpha8 = 32837,
        Luminance12Alpha4 = 32838,
        Luminance12Alpha12 = 32839,
        Luminance16Alpha16 = 32840,
        Intensity = 32841,
        Intensity4 = 32842,
        Intensity8 = 32843,
        Intensity12 = 32844,
        Intensity16 = 32845,
        Rgb2Ext = 32846,
        Rgb4 = 32847,
        Rgb5 = 32848,
        Rgb8 = 32849,
        Rgb10 = 32850,
        Rgb12 = 32851,
        Rgb16 = 32852,
        Rgba2 = 32853,
        Rgba4 = 32854,
        Rgb5A1 = 32855,
        Rgba8 = 32856,
        Rgb10A2 = 32857,
        Rgba12 = 32858,
        Rgba16 = 32859,
        DualAlpha4Sgis = 33040,
        DualAlpha8Sgis = 33041,
        DualAlpha12Sgis = 33042,
        DualAlpha16Sgis = 33043,
        DualLuminance4Sgis = 33044,
        DualLuminance8Sgis = 33045,
        DualLuminance12Sgis = 33046,
        DualLuminance16Sgis = 33047,
        DualIntensity4Sgis = 33048,
        DualIntensity8Sgis = 33049,
        DualIntensity12Sgis = 33050,
        DualIntensity16Sgis = 33051,
        DualLuminanceAlpha4Sgis = 33052,
        DualLuminanceAlpha8Sgis = 33053,
        QuadAlpha4Sgis = 33054,
        QuadAlpha8Sgis = 33055,
        QuadLuminance4Sgis = 33056,
        QuadLuminance8Sgis = 33057,
        QuadIntensity4Sgis = 33058,
        QuadIntensity8Sgis = 33059,
        DepthComponent16 = 33189,
        DepthComponent16Sgix = 33189,
        DepthComponent24 = 33190,
        DepthComponent24Sgix = 33190,
        DepthComponent32Sgix = 33191,
        DepthComponent32 = 33191,
        CompressedRed = 33317,
        CompressedRg = 33318,
        R8 = 33321,
        R16 = 33322,
        Rg8 = 33323,
        Rg16 = 33324,
        R16f = 33325,
        R32f = 33326,
        Rg16f = 33327,
        Rg32f = 33328,
        R8i = 33329,
        R8ui = 33330,
        R16i = 33331,
        R16ui = 33332,
        R32i = 33333,
        R32ui = 33334,
        Rg8i = 33335,
        Rg8ui = 33336,
        Rg16i = 33337,
        Rg16ui = 33338,
        Rg32i = 33339,
        Rg32ui = 33340,
        CompressedAlpha = 34025,
        CompressedLuminance = 34026,
        CompressedLuminanceAlpha = 34027,
        CompressedIntensity = 34028,
        CompressedRgb = 34029,
        CompressedRgba = 34030,
        DepthStencil = 34041,
        Rgba32f = 34836,
        Rgb32f = 34837,
        Rgba16f = 34842,
        Rgb16f = 34843,
        Depth24Stencil8 = 35056,
        R11fG11fB10f = 35898,
        Rgb9E5 = 35901,
        Srgb = 35904,
        Srgb8 = 35905,
        SrgbAlpha = 35906,
        Srgb8Alpha8 = 35907,
        SluminanceAlpha = 35908,
        Sluminance8Alpha8 = 35909,
        Sluminance = 35910,
        Sluminance8 = 35911,
        CompressedSrgb = 35912,
        CompressedSrgbAlpha = 35913,
        CompressedSluminance = 35914,
        CompressedSluminanceAlpha = 35915,
        CompressedSrgbS3tcDxt1Ext = 35916,
        CompressedSrgbAlphaS3tcDxt1Ext = 35917,
        CompressedSrgbAlphaS3tcDxt3Ext = 35918,
        CompressedSrgbAlphaS3tcDxt5Ext = 35919,
        DepthComponent32f = 36012,
        Depth32fStencil8 = 36013,
        Rgba32ui = 36208,
        Rgb32ui = 36209,
        Rgba16ui = 36214,
        Rgb16ui = 36215,
        Rgba8ui = 36220,
        Rgb8ui = 36221,
        Rgba32i = 36226,
        Rgb32i = 36227,
        Rgba16i = 36232,
        Rgb16i = 36233,
        Rgba8i = 36238,
        Rgb8i = 36239,
        Float32UnsignedInt248Rev = 36269,
        CompressedRedRgtc1 = 36283,
        CompressedSignedRedRgtc1 = 36284,
        CompressedRgRgtc2 = 36285,
        CompressedSignedRgRgtc2 = 36286,
    }

    #endregion

    #region enum : PixelType

    public enum PixelType
    {
        Byte = 5120,
        UnsignedByte = 5121,
        Short = 5122,
        UnsignedShort = 5123,
        Int = 5124,
        UnsignedInt = 5125,
        Float = 5126,
        HalfFloat = 5131,
        Bitmap = 6656,
        UnsignedByte332Ext = 32818,
        UnsignedByte332 = 32818,
        UnsignedShort4444Ext = 32819,
        UnsignedShort4444 = 32819,
        UnsignedShort5551 = 32820,
        UnsignedShort5551Ext = 32820,
        UnsignedInt8888 = 32821,
        UnsignedInt8888Ext = 32821,
        UnsignedInt1010102 = 32822,
        UnsignedInt1010102Ext = 32822,
        UnsignedByte233Reversed = 33634,
        UnsignedShort565 = 33635,
        UnsignedShort565Reversed = 33636,
        UnsignedShort4444Reversed = 33637,
        UnsignedShort1555Reversed = 33638,
        UnsignedInt8888Reversed = 33639,
        UnsignedInt2101010Reversed = 33640,
        UnsignedInt248 = 34042,
        UnsignedInt10F11F11FRev = 35899,
        UnsignedInt5999Rev = 35902,
        Float32UnsignedInt248Rev = 36269,
    }

    #endregion

    #region enum : PixelFormat

    public enum PixelFormat
    {
        ColorIndex = 6400,
        StencilIndex = 6401,
        DepthComponent = 6402,
        Red = 6403,
        Green = 6404,
        Blue = 6405,
        Alpha = 6406,
        Rgb = 6407,
        Rgba = 6408,
        Luminance = 6409,
        LuminanceAlpha = 6410,
        AbgrExt = 32768,
        CmykExt = 32780,
        CmykaExt = 32781,
        Bgr = 32992,
        Bgra = 32993,
        Ycrcb422Sgix = 33211,
        Ycrcb444Sgix = 33212,
        Rg = 33319,
        RgInteger = 33320,
        DepthStencil = 34041,
        RedInteger = 36244,
        GreenInteger = 36245,
        BlueInteger = 36246,
        AlphaInteger = 36247,
        RgbInteger = 36248,
        RgbaInteger = 36249,
        BgrInteger = 36250,
        BgraInteger = 36251,
    }

    #endregion

    /// <summary> This class encapsulate an OpenGL FrameBuffer </summary>
    /// <remarks> General guidelines for use:
    /// <list type="number">
    ///     <item>Create a new instance of a <c>FrameBuffer</c> object.</item>
    ///     <item>Call <c>Initialize</c> in the OpenGL initialization function.</item>
    ///     <item>Call <c>Activate</c> to draw to the framebuffer.</item>
    ///     <item>Call <c>Deactivate</c> to stop drawing to the framebuffer.</item>
    ///     <item>Call <c>Bind</c> to use the framebuffer as a texture.</item>
    /// </list>
    /// Make sure to check your card's compatability with high bit depths.
    /// Try 8 bits to significantly improve performance
    /// </remarks>
    sealed public class FrameBuffer
    {

        #region Variables

        // the Framebuffer index
        public int Id = -1;

        // the depth buffer index
        int depthBuffer = -1;

        // the texture to be used for showing the FBO
        public int TextureId = -1;

        // store the bitdepth in case of reset
        public PixelInternalFormat BitDepth = PixelInternalFormat.Rgba8;

        // the width of the render buffer
        int _width;

        // the height of the render buffer
        int _height;

        #endregion

        #region Properties

        /// <summary> Gets or Sets the Width of the Framebuffer</summary>
        public int Width
        {
            get { return _width; }
            set
            {
                _width = value;
                Resize();
            }
        }

        /// <summary> Gets or Sets the Height of the Framebuffer</summary>
        public int Height
        {
            get { return _height; }
            set
            {
                _height = value;
                Resize();
            }
        }

        /// <summary>
        /// The number of bits used to store each color channel.
        /// Note that the number of bits used to store each color is this
        /// value times four.
        /// </summary>
        public PixelInternalFormat ChannelBitDepth
        {
            get { return BitDepth; }
            set
            {
                BitDepth = value;
                while (BitDepth >= 0)
                {
                    try
                    {
                        Reset(_width, _height);
                        return;
                    }
                    catch
                    {
                        BitDepth--;
                    }
                }
                throw new Exception("FrameBufferObject could not initialize. The video card or the currect display mode may not support it.");
            }
        }

        /// <summary>The texture used to apply the stored image to a surface</summary>
        //public Texture Texture
        //{
        //    get { return texture; }
        //}

        //public TransformGL Transform { get; set; }

        #endregion

        #region Initialize

        /// <summary> Initializes all of the GL stuff and allocates any necessary memory. 
        /// This method would probably be called in GL Initialization. </summary>
        /// <param name="width">The width of the new Framebuffer</param>
        /// <param name="height">The height of the new Framebuffer</param>
        public void Initialize(int width, int height)
        {
            Initialize(width, height, BitDepth);
        }

        /// <summary> Initializes all of the GL stuff and allocates any necessary memory. 
        /// This method would probably be called in GL Initialization. </summary>
        /// <param name="width">The width of the new Framebuffer</param>
        /// <param name="height">The height of the new Framebuffer</param>
        /// <param name="BitDepth">The number of bits per color channel. (8 = Gl.GL_RGBA8, 12 = Gl.GL_RGBA12, 16 = Gl.GL_RGBA16, 32 = Gl.GL_RGBA32F_ARB)</param>
        public void Initialize(int width, int height, PixelInternalFormat pixelInternalFormat)
        {
            if (!Gl.glGetString(Gl.GL_EXTENSIONS).Contains("EXT_framebuffer_object"))
                throw new Exception("This Video Card driver doesn't support OpenGL FBO");

            width = System.Math.Max(width, 1);
            height = System.Math.Max(height, 1);

            // set the appropriate GL enum values
            PixelType pixelType = PixelType.Float;
            PixelFormat pixelFormat = PixelFormat.Rgba;
            switch (pixelInternalFormat)
            {
                case PixelInternalFormat.Rgba8:
                    pixelType = PixelType.UnsignedByte;
                    break;

                case PixelInternalFormat.Rgba12:
                case PixelInternalFormat.Rgba16:
                //case (PixelInternalFormat)ArbTextureFloat.Rgba32fArb:
                //    pixelType = PixelType.Float;
                //    break;

                case PixelInternalFormat.Rgb8:
                    pixelType = PixelType.UnsignedByte;
                    pixelFormat = PixelFormat.Rgb;
                    break;

                case PixelInternalFormat.Rgb12:
                case PixelInternalFormat.Rgb16:
                //case (PixelInternalFormat)ArbTextureFloat.Rgb32fArb:
                //    pixelType = PixelType.Float;
                //    pixelFormat = PixelFormat.Rgb;
                //    break;

                default:
                    break;
            }

            Gl.glPushAttrib(Gl.GL_LIGHTING_BIT | Gl.GL_LINE_BIT | Gl.GL_TEXTURE_BIT | Gl.GL_ENABLE_BIT | Gl.GL_ENABLE_BIT);

            // Setup our FBO
            Gl.glGenFramebuffersEXT(1, out Id);
            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, Id);

            // Create the render buffer for depth
            Gl.glGenRenderbuffersEXT(1, out depthBuffer);
            Gl.glBindRenderbufferEXT(Gl.GL_RENDERBUFFER_EXT, depthBuffer);
            Gl.glRenderbufferStorageEXT(Gl.GL_RENDERBUFFER_EXT, Gl.GL_DEPTH_COMPONENT, width, height);

            // error check
            //CheckError();

            // Now setup a texture to render to
            Gl.glEnable(Gl.GL_TEXTURE_2D);
            Gl.glGenTextures(1, out TextureId);

            Gl.glDisable(Gl.GL_TEXTURE_3D_EXT);
            Gl.glDisable(Gl.GL_TEXTURE_2D);

            Gl.glEnable(Gl.GL_TEXTURE_2D);
            Gl.glActiveTexture(Gl.GL_TEXTURE0);
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, TextureId);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);

            Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, (int)pixelInternalFormat, width, height, 0, (int)pixelFormat, (int)pixelType, IntPtr.Zero);

            // error check
            //CheckError();

            // And attach it to the FBO so we can render to it
            Gl.glFramebufferTexture2DEXT(Gl.GL_FRAMEBUFFER_EXT, Gl.GL_COLOR_ATTACHMENT0_EXT, Gl.GL_TEXTURE_2D, TextureId, 0);

            // Attach the depth render buffer to the FBO as it's depth attachment
            Gl.glFramebufferRenderbufferEXT(Gl.GL_FRAMEBUFFER_EXT, Gl.GL_DEPTH_ATTACHMENT_EXT, Gl.GL_RENDERBUFFER_EXT, depthBuffer);

            // error check
            CheckError();

            // Unbind the FBO for now
            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, 0);

            Gl.glPopAttrib();
            _width = width;
            _height = height;
            this.BitDepth = pixelInternalFormat;
        }

        public void FBOInitializeView(bool perspectiveProjection, float z)
        {
            double height = System.Math.Max(this.Height, 1);
            double width = System.Math.Max(this.Width, 1);
            Gl.glViewport(0, 0, (int)width, (int)height);
            double aspectRatio = width / height;
            Gl.glMatrixMode(Gl.GL_PROJECTION);                   // Select The Projection Matrix
            Gl.glLoadIdentity();                                      // Reset The Projection Matrix

            var ratio = this.Width / (double)this.Height;
            if (perspectiveProjection)
                Glu.gluPerspective(45, ratio, .01, 1000.0);
            else
            {
                double size = z * -.4;
                Gl.glOrtho(-ratio * size, ratio * size, -size, size, -100, 100);
            }
            Gl.glMatrixMode(Gl.GL_MODELVIEW);                    // Select The Modelview Matrix
            Gl.glLoadIdentity();                                      // Reset The Modelview Matrix
        }

        #endregion

        #region Activate

        /// <summary> Start rendering to this framebuffer. </summary>
        public void Activate()
        {
            // First we bind the FBO so we can render to it
            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, Id);

            // Save the view port and set it to the size of the texture
            //Gl.glPushAttrib(Gl.GL_VIEWPORT_BIT);
            //Gl.glViewport(0, 0, GlControl.Width, GlControl.Height);
        }

        #endregion

        #region Deactivate

        /// <summary> Stop rendering to this framebuffer, and render to the screen. </summary>
        public void Deactivate()
        {
            //Gl.glPopAttrib();
            Gl.glBindFramebufferEXT(Gl.GL_FRAMEBUFFER_EXT, 0);
        }

        #endregion

        #region Bind

        /// <summary> Set this framebuffer as the current texture. </summary>
        public void Bind()
        {
            Gl.glDisable(Gl.GL_TEXTURE_3D_EXT);
            Gl.glDisable(Gl.GL_TEXTURE_2D);

            Gl.glEnable(Gl.GL_TEXTURE_2D);
            Gl.glActiveTexture(Gl.GL_TEXTURE0);
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, TextureId);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);
            Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR_MIPMAP_LINEAR);
        }

        #endregion

        #region Destroy

        /// <summary> Destroy this framebuffer and free any allocated resources. </summary>
        public void Destroy()
        {
            Gl.glDeleteFramebuffersEXT(1, ref Id);
            Gl.glDeleteRenderbuffersEXT(1, ref depthBuffer);

            if (TextureId > -1)
            {
                Gl.glPushAttrib(Gl.GL_TEXTURE_BIT | Gl.GL_ENABLE_BIT);
                Gl.glEnable(Gl.GL_TEXTURE_2D);
                Gl.glDeleteTextures(1, ref TextureId);
                Gl.glPopAttrib();
            }

            Id = -1;
            depthBuffer = -1;
        }

        #endregion

        #region Reset

        /// <summary> Reinitialize the framebuffer with a new size. </summary>
        /// <remarks> Setting the <c>Width</c> or <c>Height</c> properties calls this method. </remarks>
        /// <param name="width">The new width of the Framebuffer</param>
        /// <param name="height">The new height of the Framebuffer</param>
        public void Reset(int width, int height)
        {
            if (Id > -1)
                Destroy();
            Initialize(width, height, BitDepth);
        }

        #endregion

        #region Resize

        public void Resize()
        {
            Gl.glPushAttrib(Gl.GL_ENABLE_BIT | Gl.GL_TEXTURE_BIT);

            Bind();

            Gl.glRenderbufferStorageEXT(Gl.GL_FRAMEBUFFER_EXT, Gl.GL_DEPTH_COMPONENT, _width, _height);

            Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, (int)PixelInternalFormat.Rgba8, _width, _height, 0, (int)PixelFormat.Rgba, (int)PixelType.UnsignedByte, IntPtr.Zero);

            Gl.glPopAttrib();
        }

        #endregion

        #region CheckError

        private void CheckError()
        {
            int status = Gl.glCheckFramebufferStatusEXT(Gl.GL_FRAMEBUFFER_EXT);

            switch (status)
            {
                case Gl.GL_FRAMEBUFFER_COMPLETE_EXT:
                    return;
                case Gl.GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT:
                case Gl.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT:
                case Gl.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT:
                //case Gl.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT:
                case Gl.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT:
                case Gl.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT:
                case Gl.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT:
                case Gl.GL_FRAMEBUFFER_BINDING_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
                case Gl.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
                case Gl.GL_FRAMEBUFFER_UNSUPPORTED_EXT:
                    throw new Exception("The card may not be compatible with Framebuffers. Try another bit depth. Status:" + status);
            }

            throw new Exception("The card may not be compatible with Framebuffers. Try another bit depth. Status:" + status);
        }

        #endregion

    }
}
